// vim:syntax=c
#usage "<b>Generate a user defined silk screen</b>\n"
       "<p>"
       "Some board manufacturers want to have at least a width of 8mil "
       "for silk screen lines in order to guarantee legible results. "
       "EAGLE libraries use 5 mil width for silk screen as default. "
       "<p>"
       "This ULP changes all silk screen elements to a minimum "
       "width supplied by user. All elements of layers 20, 21, 22, 25, 26 "
       "are written into new layers 121(_tsilk) and 122 (_bsilk). "
       "Texts are changes as well. The new ratio is set to minimum value "
       "that is requred to achieve the silk wire width. If the original "
       "text ratio is greater, it is not changed. "
       "<p>"
       "Two new layers will be defined and the new silk screen will be "
       "generated. For generating GERBER data be aware that you have to "
       "activate layers 121 or 122 instead of the original layers."
       "<p>"
       "<author>Original authors: Richard Hammerl 26-05-1998, "
       "Changed for EAGLE 4.0  26-02-2002, support@cadsoft.de "
       "Fixed for EAGLE 4.11, OLIMEX special 04-11-2003, Y.Onodera, "
       "Further fixed and modified by Antti Arola 11.03.2005 "
       "user interface, general code cleanup, ability to delete new "
       "layers before re-creating them, and board-level text support "
       "added by M.Cuddy 20.03.2005"
       "</author>"

// THIS PROGRAM IS PROVIDED AS IS AND WITHOUT WARRANTY OF ANY KIND, EXPRESSED OR IMPLIED

// Define your own silk screen width here, in mils
real Silkwidth  = 8.0;  // Default value suggested by dialog

// layer name constants
int  dimension = LAYER_DIMENSION,
     tplace = LAYER_TPLACE,
     bplace = LAYER_BPLACE,
     tnames = LAYER_TNAMES,
     bnames = LAYER_BNAMES,
     tvalues = LAYER_TVALUES,
     bvalues = LAYER_BVALUES,
     _tsilk = LAYER_TPLACE + LAYER_USER,
     _bsilk = LAYER_BPLACE + LAYER_USER;

// name of new, generated layers
string _tsilkName = "_tsilk";
string _bsilkName = "_bsilk";

// boolean flags for which layers to operate on
int do_tplace = 1,
    do_bplace = 1,
    do_tnames = 1,
    do_bnames = 1,
    do_tvalues = 1,
    do_bvalues = 1;

int erase_silk = 1,
    warn_ratio = 1;

int eraseOnly = 0;

// built-up command string
string cmd = "";

// 'curLayer', 'curRatio', and 'curSize' are cached globals so we don't
// generate more script commands than needed.
int curLayer = -1;    // start as an invalid number, so first call will
int curRatio = -1;    // always set
real curSize = -1.0;    

void setLayer(int dstlay) 
{
    string h;
    if (dstlay == curLayer) return;
    curLayer = dstlay;
    sprintf(h,"LAYER %d;\n",curLayer); 
    cmd += h;
}

void setRatio(int newRatio)
{
    string h;
    if (curRatio == newRatio) return;
    curRatio = newRatio;
    sprintf(h, "CHANGE RATIO %d;\n", curRatio); 
    cmd += h;
}

void setSize(real newSize)
{
    string h;
    if (curSize == newSize) return;
    curSize = newSize;
    sprintf(h, "CHANGE SIZE %5.3f;\n", curSize); 
    cmd += h;
}


void del_layer(int layer, real x1, real y1, real x2, real y2)
{
    string h;
    sprintf(h, "DISPLAY NONE %d;\n", layer); cmd += h;
    sprintf(h, "GROUP (>%f %f) (%f %f) (%f %f) (%f %f) (>%f %f);\n",
              x1, y1, x1, y2, x2, y2, x2, y1, x1, y1 ); cmd += h;
    sprintf(h, "DELETE (>%f %f);\n", x1, x2 ); cmd += h;
    sprintf(h, "LAYER ?? -%d;\n", layer); cmd += h;
    sprintf(h, "WINDOW;\n" ); cmd += h;
}

void erase_silk_layers(UL_BOARD B)
{
      string h;
      real x1,x2,y1,y2;

      // size of board area..
      x1 = u2mil(B.area.x1);
      x2 = u2mil(B.area.x2);
      y1 = u2mil(B.area.y1);
      y2 = u2mil(B.area.y2);

      // delete silk screen layers
      string cur_active_layers = "";
      int got_a_silk = 0;

      // find currently active layers, exclude _tsilk and _bsilk from the
      // mix.
      B.layers(L) {
          if (L.number == _tsilk || L.number == _bsilk ) {
              got_a_silk = 1;
              continue;
          } else if (L.visible) {
              sprintf(h," %d", L.number); cur_active_layers += h;
          }
      }

      if (got_a_silk) {
          // hide all layers
          sprintf(h, "DISPLAY NONE;\n"); cmd += h;

          // remove the _tsilk and _bsilk layers
          B.layers(L) {
              if (L.number == _tsilk || L.number == _bsilk ) {
                  del_layer(L.number,x1,y1,x2,y2);
              }
          }
          // restore previously visible layers
          sprintf(h, "DISPLAY %s;\n", cur_active_layers); cmd += h;
      }
}

void header(UL_BOARD B) 
{
    string h;
    if (erase_silk) {
        erase_silk_layers(B);
    }

    // create the new layers.
    sprintf(h, "LAYER %d %s;\n", _tsilk, _tsilkName);cmd += h; 
    sprintf(h, "LAYER %d %s;\n", _bsilk, _bsilkName);cmd += h;
    sprintf(h, "SET COLOR_LAYER %d YELLOW;\n", _tsilk);cmd += h; // and
    sprintf(h, "SET COLOR_LAYER %d YELLOW;\n", _bsilk);cmd += h; // colors

    sprintf(h, "SET WIRE_BEND 2;\n\n");cmd += h;
}

real calcwidth(int size, int ratio)  // returns mils
{
    return (u2mil(size) * (ratio/100.0));
}

int getreqratio(int size)            // returns integer
{
    return ceil((Silkwidth / u2mil(size)) * 100);
}

void do_text(string ename, UL_TEXT T, int dstlay, string orient)
{
    real OrigWidth;
    int newRatio;
    string value;
    string h;

    if (T.value == "") return;

    OrigWidth = calcwidth(T.size, T.ratio);

    if (OrigWidth < Silkwidth)
        newRatio = getreqratio(T.size);
    else
        newRatio = T.ratio;

    // Check if required ratio is >31 (maximum), give warning
    if (newRatio > 31) {
        if (warn_ratio) {
            sprintf(h, "Element %s non-smashed text \"%s\":\nsize is %5.3f mils, required ratio %d\nUsing maximum ratio (31).\n",
                ename, T.value, u2mil(T.size), newRatio);
            dlgMessageBox(h);
        }
        newRatio = 31;
    }

    setLayer(dstlay);
    setRatio(newRatio);
    setSize(u2mil(T.size));

    // double-up single quotes in text.
    value = T.value;
    if (strchr(value,'\'') != -1) {
        string ary[];
        int n = strsplit(ary,value,'\'');
        int i;
        value = "";
        for (i = 0; i < n; i++) {
            if (i != 0) value += "''";
            value += ary[i];
        }
    }
    sprintf(h, "TEXT '%s' %s%1.0f (%5.3f %5.3f);\n",
         value, orient, T.angle, u2mil(T.x), u2mil(T.y));
    cmd += h;
}

/* given a source layer, return the correct destination layer */
int getDstLayer(int source, int dimrun)
{
    int dstlay;
    if (source == bvalues || 
        source == bplace || 
        source == bnames || 
        (source == dimension && dimrun == 2)) {
            dstlay = _bsilk;
    } else {
        dstlay = _tsilk;
    }
    return dstlay;
}

// make sure curWidth (in mils) to no less than Silkwidth
real clampSilkWidth(real curWidth)
{
    if (curWidth < Silkwidth)
        return Silkwidth;
    return curWidth;
}

void searchElements(UL_ELEMENT E, int source, int dimrun, string orient) 
{
    int dstlay = getDstLayer(source, dimrun);
    real sw;
    string h;

    // iterate over wires in element
    E.package.wires(W) {
        // fixed arcs for v4.11
        if (W.arc) {
            if (W.arc.layer != source) continue;
            sw = clampSilkWidth(u2mil(W.arc.width));
            setLayer(dstlay);
            sprintf(h, 
                "ARC %5.3f ccw (%5.3f %5.3f) (%5.3f %5.3f) (%5.3f %5.3f);\n",
                    sw, 
                    u2mil(W.arc.x1), u2mil(W.arc.y1), 
                    u2mil(2*(W.arc.xc)-W.arc.x1),u2mil(2*(W.arc.yc)-W.arc.y1),
                    u2mil(W.arc.x2), u2mil(W.arc.y2));
                cmd += h;
        } else {
            if (W.layer != source) continue;
            sw = clampSilkWidth(u2mil(W.width));
            setLayer(dstlay);
            sprintf(h, "WIRE %5.3f (%5.3f %5.3f) (%5.3f %5.3f);\n",
                Silkwidth, u2mil(W.x1), u2mil(W.y1), u2mil(W.x2), u2mil(W.y2));
            cmd += h;
        }
    }
    E.package.circles(C) {
        if (C.layer != source) continue;

        sw = clampSilkWidth(u2mil(C.width));
        setLayer(dstlay);
        sprintf(h, "CIRCLE %5.3f (%5.3f %5.3f) (%5.3f %5.3f);\n",
           sw, u2mil(C.x), u2mil(C.y), u2mil(C.x + C.radius), u2mil(C.y));
           cmd += h;
    }
    // fixed angle
    // Removed TextOrientation string, always "R"
    E.package.rectangles(R) {
        if (R.layer != source) continue;
        setLayer(dstlay);
        sprintf(h, "RECT R%1.0f (%5.3f %5.3f) (%5.3f %5.3f);\n",
            R.angle, u2mil(R.x1), u2mil(R.y1), u2mil(R.x2), u2mil(R.y2));
        cmd += h;
    }
    E.package.polygons(P) {
        int first;
        if (P.layer != source) continue;
        sw = clampSilkWidth(P.width);
        first = 1;
        P.wires(WP) {
            if (first) {
                sprintf(h, "POLYGON %5.3f (%5.3f %5.3f)", 
                    sw, u2mil(WP.x1), u2mil(WP.y1));
                cmd += h;
            } else {
                sprintf(h, "\n    %+f (%5.3f %5.3f)", 
                    WP.curve, u2mil(WP.x2), u2mil(WP.y2));
                cmd += h;
            }
            first = 0;
        }
        sprintf(h, ";\n");cmd += h;
    }
    // non-smashed texts
    E.package.texts(T) {
        if (T.layer == source)
            do_text(E.name, T, dstlay, orient);
    }
    // smashed texts
    E.texts(T) {
        if (T.layer == source)
            do_text(E.name, T, dstlay, orient);
    }
}

void searchTexts(UL_TEXT T, int source, int dimrun, string orient)
{
    int dstlay = getDstLayer(source, dimrun);
    if (T.layer == source)
        do_text("", T, dstlay, orient);
}

string settingsFile()
{
    string fn; 
    string boardName;

    project.board(B) { boardName = B.name; }
    fn = filesetext(boardName,".silk");
    return fn;
}

void saveSettings()
{
    string fn, data;
    string lines[], words[];
    int nlines, nwords;

    fn = settingsFile();
    output(fn,"w") {
        printf("Silkwidth=%f\n", Silkwidth);
        printf("dimension=%d\n", dimension);
        printf("tplace=%d\n", tplace);
        printf("bplace=%d\n", bplace);
        printf("tnames=%d\n", tnames);
        printf("bnames=%d\n", bnames);
        printf("tvalues=%d\n", tvalues);
        printf("bvalues=%d\n", bvalues);
        printf("_tsilk=%d\n", _tsilk);
        printf("_bsilk=%d\n", _bsilk);
        printf("_tsilkName=%s\n", _tsilkName);
        printf("_bsilkName=%s\n", _bsilkName);
        printf("do_tplace=%d\n", do_tplace);
        printf("do_bplace=%d\n", do_bplace);
        printf("do_tnames=%d\n", do_tnames);
        printf("do_bnames=%d\n", do_bnames);
        printf("do_tvalues=%d\n", do_tvalues);
        printf("do_bvalues=%d\n", do_bvalues);
        printf("erase_silk=%d\n", dimension);
        printf("warn_ratio=%d\n", dimension);
    }
}

void loadSettings()
{
    string fn, data;
    string lines[], words[];
    int nlines, nwords, i;

    fn = settingsFile();
    fileerror();        // clear any previous error.
    fileread(data,fn);
    // it's okay if the settings file can't be read.
    if (fileerror()) return;

    nlines = strsplit(lines, data,'\n');
    for (i = 0; i < nlines; i++) {
        nwords = strsplit(words,lines[i],'=');
        if (nwords != 2) continue;
        if (words[0] == "Silkwidth") {
            Silkwidth = strtod(words[1]);
        } else if (words[0] == "dimension") {
            dimension = strtol(words[1]);
        } else if (words[0] == "tplace") {
            tplace = strtol(words[1]);
        } else if (words[0] == "bplace") {
            bplace = strtol(words[1]);
        } else if (words[0] == "tnames") {
            tnames = strtol(words[1]);
        } else if (words[0] == "bnames") {
            bnames = strtol(words[1]);
        } else if (words[0] == "tvalues") {
            tvalues = strtol(words[1]);
        } else if (words[0] == "bvalues") {
            bvalues = strtol(words[1]);
        } else if (words[0] == "_tsilk") {
            _tsilk = strtol(words[1]);
        } else if (words[0] == "_bsilk") {
            _bsilk = strtol(words[1]);
        } else if (words[0] == "_tsilkName") {
            _tsilkName = words[1];
        } else if (words[0] == "_bsilkName") {
            _bsilkName = words[1];
        } else if (words[0] == "do_tplace") {
            do_tplace = strtol(words[1]);
        } else if (words[0] == "do_bplace") {
            do_bplace = strtol(words[1]);
        } else if (words[0] == "do_tnames") {
            do_tnames = strtol(words[1]);
        } else if (words[0] == "do_bnames") {
            do_bnames = strtol(words[1]);
        } else if (words[0] == "do_tvalues") {
            do_tvalues = strtol(words[1]);
        } else if (words[0] == "do_bvalues") {
            do_bvalues = strtol(words[1]);
        } else if (words[0] == "erase_silk") {
            erase_silk = strtol(words[1]);
        } else if (words[0] == "warn_ratio") {
            warn_ratio = strtol(words[1]);
        }
    }
}

if (! board) {
    dlgMessageBox("\n    Start this ULP in a Board    \n");
    exit (0);
}


board(B) {
    string h;
    int rc;

    loadSettings();
    rc = dlgDialog ("Enter silkscreen width") {
      dlgVBoxLayout {
        dlgHBoxLayout {
          dlgLabel ("Minimum silkscreen wire width in mils:");
          dlgRealEdit (Silkwidth, 0.0, 99.9);
        }
        dlgLabel ("Source layers:");
        dlgHBoxLayout {
          dlgLabel ("Place   top:");
          dlgIntEdit (tplace, 0, 999);
          dlgCheckBox ("", do_tplace);
          dlgLabel ("  bottom:");
          dlgIntEdit (bplace, 0, 999);
          dlgCheckBox ("", do_bplace);
        }
        dlgHBoxLayout {
          dlgLabel ("Names   top:");
          dlgIntEdit (tnames, 0, 999);
          dlgCheckBox ("", do_tnames);
          dlgLabel ("  bottom:");
          dlgIntEdit (bnames, 0, 999);
          dlgCheckBox ("", do_bnames);
        }
        dlgHBoxLayout {
          dlgLabel ("Values   top:");
          dlgIntEdit (tvalues, 0, 999);
          dlgCheckBox ("", do_tvalues);
          dlgLabel ("  bottom:");
          dlgIntEdit (bvalues, 0, 999);
          dlgCheckBox ("", do_bvalues);
        }
        dlgLabel ("Generated Silk screen layers:");
        dlgHBoxLayout {
          dlgLabel ("top layer number:");
          dlgIntEdit (_tsilk, 100, 999);
          dlgLabel ("  name:");
          dlgStringEdit (_tsilkName);
        }
        dlgHBoxLayout {
          dlgLabel ("bottom layer number:");
          dlgIntEdit (_bsilk, 100, 999);
          dlgLabel ("  name:");
          dlgStringEdit (_bsilkName);
        }
        dlgHBoxLayout {
          dlgCheckBox ("Erase existing tSilk/bSilk layer:", erase_silk);
          dlgCheckBox ("Warn about invalid text ratios", warn_ratio);
        }
        dlgHBoxLayout {
            dlgPushButton ("+Make Silkscreen layers") dlgAccept (1);
            dlgPushButton ("Erase old silkscreen") dlgAccept(2);
        }
      }
    };

    if (rc == 2) {
        eraseOnly = 1;
    } else {
        int err = 0;

        eraseOnly = 0;
        if (do_tplace+do_bplace+do_tnames+do_bnames+do_tvalues+do_bvalues == 0){
            dlgMessageBox("No source layers selected.");
            exit(0);
        }

        // make sure that new layers we're about to create don't exist already
        B.layers(L) {
            if (L.name == _tsilkName && L.number != _tsilk) {
                sprintf(h,"Duplicate top-silk-layer name '%s' with different layer number (%d != %d)\nTry erasing old silk-screen layers first.", L.name, L.number, _tsilk );
                dlgMessageBox(h);
                err = 1;
            }
            if (L.name == _bsilkName && L.number != _bsilk) {
                sprintf(h,"Duplicate bottom-silk-layer name '%s' with different layer number (%d != %d)\nTry erasing old silk-screen layers first.", L.name, L.number, _tsilk );
                dlgMessageBox(h);
                err = 1;
            }
        }
        if (err) exit(1);
    }

    sprintf(h, "\nGRID mil;\n\n");cmd += h;

    if (eraseOnly) {
        erase_silk_layers(B);
    } else {
        cmd += "SET UNDO_LOG OFF;\n"; // advisable for speed reasons
        header(B);
        // board level text.
        B.texts(T) {
            searchTexts(T,dimension,1,"R");
            // draw dimensions for bottom side
            searchTexts(T,dimension,2,"MR");

            if (do_tplace) 
                searchTexts(T,tplace,0,"R");

            if (do_bplace) 
                searchTexts(T,bplace,0,"MR");

            if (do_tnames) 
                searchTexts(T,tnames,0,"R");
              
            if (do_bnames) 
                searchTexts(T,bnames,0,"MR");

            if (do_tvalues) 
                searchTexts(T,tvalues,0,"R");
      
            if (do_bvalues)
                searchTexts(T,bvalues,0,"MR");
        }

        B.elements(E) {
            // draw dimensions for top side
            searchElements(E,dimension,1,"R");

            // draw dimensions for bottom side
            searchElements(E,dimension,2,"MR");

            if (do_tplace)
              searchElements(E,tplace,0,"R");

            if (do_bplace)
              searchElements(E,bplace,0,"MR");

            if (do_tnames)
              searchElements(E,tnames,0,"R");

            if (do_bnames) 
              searchElements(E,bnames,0,"MR");

            if (do_tvalues) 
              searchElements(E,tvalues,0,"R");

            if (do_bvalues)
              searchElements(E,bvalues,0,"R");
        }
        cmd += "SET UNDO_LOG ON;\n";
    }
}

int Result = dlgDialog("Script to generate the new silk screen") {
    dlgVBoxLayout {
        dlgTextEdit(cmd);
        dlgHBoxLayout {
            dlgSpacing(300);
            dlgStretch(1);
            dlgPushButton("+Execute") dlgAccept();
            dlgPushButton("-Cancel") dlgReject();
        }
    }
};

if (Result == 0) exit(0);
if (!eraseOnly) saveSettings();

exit(cmd);
